/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gui;

import commands.controller.CC_GUIAddDownload;
import commands.controller.CC_GUIAddShared;
import commands.controller.CC_GUIDelDownload;
import commands.controller.CC_GUIDelShared;
import commands.controller.CC_GUIMoveDownloadTask;
import commands.controller.CC_GUISearch;
import commands.controller.CC_GUISetPreferences;
import commands.controller.CC_GUIStartDownloadFile;
import commands.controller.CC_GUIStopDownloadFile;
import commands.controller.ControllerCommand;
import dataTypes.DownloadFile;
import dataTypes.DownloadFile.DownloadStateType;
import dataTypes.File;
import dataTypes.Host;
import gui.dataTypes.DownloadTask;
import gui.dataTypes.SearchRecord;
import gui.dataTypes.SharedFile;
import java.awt.Window;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.EventObject;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Vector;
import javax.accessibility.AccessibleContext;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.tree.TreeModel;
import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;

/**
 *
 * @author Marcin Kamionowski
 */
public class P2pApp extends SingleFrameApplication {

    static private P2pApp manager = null;
    private DefaultTableModel sharedTableModel = null;
    private DefaultTableModel downloadTableModel = null;
    private DefaultTableModel activeHostsTableModel = null;
    private List<DownloadFile> downloadingFiles = null;
    private List<SharedFile> sharedFiles = null;
    private HashMap<Integer, DefaultTableModel> searchTableModels = null;
    private HashMap<Integer, List<File>> searchResults = null;
    private HashMap<String, Integer> activeHosts = null;
    //private AccessibleContext windowContext;
    private PleaseWaitWindow waitingWin;
    private static P2pView view;
    private GUI guiCtrl;
    private boolean getPropertiesRequested = false;
    private boolean getActiveHostsRequested = false;
    int searchCount = 0;
    private String currentDir = null;

    public static void launch(String[] args) {
        System.out.println("LAUNCH");
        launch(P2pApp.class, args);
    }

    public static P2pApp getManager() {
        while (manager == null || view == null) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return manager;
    }

    public static P2pApp getApplication() {
        return Application.getInstance(P2pApp.class);
    }

    public void setGUICtrl(GUI gui) {
        guiCtrl = gui;
    }

    protected void remSearchPanel(int id) {
        view.remSearchPanel(id);
        searchTableModels.remove(new Integer(id));
    }

    protected void validateShared(List<File> list) {
        SharedFile itemDisplayed, itemCreated;
        File itemGot;
        int firstEdited = -1, lastEdited = -1, currentDisplayed = -1;
        int charIndex;
        String filename;
        String path;

        List<SharedFile> tmpList = new LinkedList<SharedFile>();
        if (sharedFiles == null) {
            sharedFiles = new LinkedList<SharedFile>();
        }
        ListIterator<SharedFile> iterDisplayed = sharedFiles.listIterator();
        ListIterator<File> iterSended = list.listIterator();

        outer:
        while (iterSended.hasNext()) {
            //create new list of shared files
            itemGot = iterSended.next();
            charIndex = itemGot.getPath().lastIndexOf(File.separator);
            if (charIndex == -1) {
                charIndex = 0;
            }
            filename = itemGot.getPath().substring(charIndex + 1, itemGot.getPath().length());
            if (charIndex == 0) {
                path = "";
            } else {
                path = itemGot.getPath().substring(0, charIndex);
            }
            itemCreated = new SharedFile(filename, path);
            tmpList.add(itemCreated);

            while (iterDisplayed.hasNext()) {
                //both list still have some elements
                itemDisplayed = iterDisplayed.next();
                currentDisplayed++;
                if (itemDisplayed.equals(itemCreated)) {
                    //nothing changed
                    continue outer;
                } else {
                    //remove displayed row
                    sharedTableModel.removeRow(currentDisplayed);
                    if (firstEdited != -1) {
                        firstEdited = currentDisplayed;
                    }
                    currentDisplayed--;
                }
            }
            //displayed list haven't got elemetes - need to add
            if (firstEdited != -1) {
                firstEdited = currentDisplayed;
            }
            Object data[] = new Object[2];
            data[SharedFile.getFilenameId()] = itemCreated.getFilename();
            data[SharedFile.getPathId()] = itemCreated.getPath();
            sharedTableModel.addRow(data);
            currentDisplayed++;
        }
        while (iterDisplayed.hasNext()) {
            //need to remove elements if displayed list is longer than new
            itemDisplayed = iterDisplayed.next();
            sharedTableModel.removeRow(currentDisplayed + 1);
        }
        lastEdited = sharedTableModel.getRowCount();
        //TODO
        //sharedTableModel.fireTableRowsDeleted(firstEdited, lastEdited);
        sharedTableModel.fireTableDataChanged();
        sharedFiles = tmpList;
    }

    protected void validateDownloading(List<DownloadFile> list) {
        DownloadFile itemDisplayed, itemSended;
        NumberFormat numberFormat = NumberFormat.getPercentInstance(Locale.US);
        int firstRemoved = -1, lastRemoved = -1, currentDisplayed = -1, currentSended = 0;
        int rowIdOrigin = 0, firstAdded = -1, lastAdded = -1;
        boolean edited = false;

        List<DownloadFile> tmpList = new LinkedList<DownloadFile>();
        if (downloadingFiles == null) {
            downloadingFiles = new LinkedList<DownloadFile>();
        }
        ListIterator<DownloadFile> iterDisplayed = downloadingFiles.listIterator();
        ListIterator<DownloadFile> iterSended = list.listIterator();

        outer:
        while (iterSended.hasNext()) {
            //create new list of shared files
            itemSended = iterSended.next();
            currentSended++;
            tmpList.add(itemSended);

            while (iterDisplayed.hasNext()) {
                //both list still have some elements
                itemDisplayed = iterDisplayed.next();
                currentDisplayed++;
                if (itemDisplayed.equals(itemSended)) {
                    //only state can be diff.
                    if (edited) {
                        downloadTableModel.setValueAt(currentSended, currentSended - 1, DownloadTask.getPosId());
                    }
                    //check state of download
                    if (!itemDisplayed.getDownloadState().equals(itemSended.getDownloadState())) {
                        itemDisplayed.setDownloadState(itemSended.getDownloadState());
                        itemDisplayed.setDownloadedPartsNumber(itemSended.getDownloadedPartsNumber());
                        downloadTableModel.setValueAt(
                                itemDisplayed.getDownloadState(),
                                currentSended - 1,
                                DownloadTask.getStateId());
                        downloadTableModel.fireTableCellUpdated(currentSended, DownloadTask.getStateId());
                    }
                    downloadTableModel.setValueAt(
                            numberFormat.format(
                            ((double) itemSended.getDownloadedPartsNumber()) /
                            ((double) itemSended.getNumberOfParts())),
                            currentSended - 1,
                            DownloadTask.getProgId());
                    downloadTableModel.fireTableCellUpdated(currentSended, DownloadTask.getProgId());

                    // fire delete
                    if (firstRemoved != -1) {
                        downloadTableModel.fireTableRowsDeleted(firstRemoved, lastRemoved);
                        firstRemoved = lastRemoved = -1;
                        rowIdOrigin = currentDisplayed;
                    }
                    continue outer;
                } else {
                    //remove displayed row
                    downloadTableModel.removeRow(currentDisplayed);

                    edited = true;
                    if (firstRemoved != -1) {
                        firstRemoved = currentDisplayed;
                    }
                    lastRemoved = rowIdOrigin;
                    currentDisplayed--;
                }
                rowIdOrigin++;
            }
            // fire delete
            if (firstRemoved != -1) {
                downloadTableModel.fireTableRowsDeleted(firstRemoved, lastRemoved);
                firstRemoved = lastRemoved = -1;
            }
            //displayed list haven't got elemetes - need to add
            Object data[] = new Object[4];
            data[DownloadTask.getFilenameId()] = itemSended.getPath();
            data[DownloadTask.getStateId()] = itemSended.getDownloadState();
            //TODO
            data[DownloadTask.getProgId()] =
                    numberFormat.format(
                    ((double) itemSended.getDownloadedPartsNumber()) /
                    ((double) itemSended.getNumberOfParts()));
            data[DownloadTask.getPosId()] = currentSended;
            if (firstAdded == -1) {
                firstAdded = currentSended - 1;
            }
            lastAdded = rowIdOrigin;
            downloadTableModel.addRow(data);
            currentDisplayed++;
        }
        if (firstAdded != -1) {
            downloadTableModel.fireTableRowsInserted(firstAdded, lastAdded);
        }
        while (iterDisplayed.hasNext()) {
            //need to remove elements if displayed list is longer than new
            itemDisplayed = iterDisplayed.next();
            downloadTableModel.removeRow(currentDisplayed + 1);
        }
        if (firstRemoved != -1) {
            downloadTableModel.fireTableRowsDeleted(firstRemoved, lastRemoved);
        }
        downloadingFiles = tmpList;
        view.refreshButtonsStates();
    }

    protected void displayAddSharedWindow() {
        JFileChooser chooser = new JFileChooser();
        int returnVal = chooser.showOpenDialog(getApplication().getMainFrame());
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            guiCtrl.writeOutCommand(new CC_GUIAddShared(
                    chooser.getSelectedFile().getParent(),
                    chooser.getSelectedFile().getName()));
        }

    }

    protected void displayDownloadFinished(String file) {
        JOptionPane.showMessageDialog(
                getMainFrame(),
                file,
                "Download finished",
                JOptionPane.INFORMATION_MESSAGE);
    }

    protected void displayErrorWin(String file, String msg) {
        JOptionPane.showMessageDialog(
                getMainFrame(),
                "Cannot download file (" + file + "):\n" + msg,
                "Download error",
                JOptionPane.ERROR_MESSAGE);
    }

    protected void displayPropertiesWin(int maxActiveConnections, int connectionsPerDownload) {
        if (getPropertiesRequested) {
            getPropertiesRequested = false;
            hideWaitingWin();
            view.showPreferencesWindow(maxActiveConnections, connectionsPerDownload);
        }
    }

    protected void ignoreDisplayPropertiesWin() {
        System.out.println("GUI: ignore action");
        getPropertiesRequested = false;
    }

    protected void addSearchResult(int id, File file, Host host) {
        DefaultTableModel tableModel = searchTableModels.get(id);
        if (tableModel == null) {
            System.err.println("GUI: Wrong id search in received search result");
            return;
        }
        List<File> list = searchResults.get(id);
        if (list == null) {
            System.err.println("GUI: Wrong id in search result");
            return;
        }
        list.add(file);
        Object data[] = new Object[SearchRecord.getColumnsCount()];
        data[SearchRecord.getNamePos()] = file.getPath();
        data[SearchRecord.getSizePos()] = file.getSize();
        data[SearchRecord.getHashPos()] = file.getHash();
        data[SearchRecord.getHostPos()] = host.getHostName();
        //TODO: hit rate - unimplemanted
        //data[SearchRecord.getHitratePos()] = 1;
        tableModel.addRow(data);
        tableModel.fireTableRowsInserted(tableModel.getRowCount(),
                tableModel.getRowCount());

    }

    public void outAddDownload(int id, int idRow) {
        List<File> list = searchResults.get(id);
        if (list == null) {
            System.err.println("GUI: Wrong id in search result");
            return;
        }
        JFileChooser chooser;
        File file = list.get(idRow);

        if (currentDir == null) {
            chooser = new JFileChooser();
            currentDir = System.getProperty("user.home");
        } else {
            chooser = new JFileChooser(currentDir);
        }
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setDialogType(JFileChooser.SAVE_DIALOG);
        chooser.setMultiSelectionEnabled(false);
        chooser.setApproveButtonText("Save");
        chooser.setSelectedFile(new java.io.File(currentDir, file.getPath()));
        while (true) {
            int returnVal = chooser.showDialog(getApplication().getMainFrame(), "Save as");
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                if (chooser.getSelectedFile().getAbsoluteFile().exists()) {
                    int n = JOptionPane.showConfirmDialog(
                            chooser,
                            "File exists. Would you like to overwrite?",
                            "Confirm...",
                            JOptionPane.YES_NO_OPTION);
                    if (n == JOptionPane.NO_OPTION) {
                        continue;
                    }
                }
                currentDir = chooser.getSelectedFile().getParent();
                guiCtrl.writeOutCommand(
                        new CC_GUIAddDownload(chooser.getSelectedFile().getName(),
                        chooser.getSelectedFile().getParent(),
                        file.getSize(),
                        file.getHash()));
                break;
            } else {
                break;
            }
        }
    }

    @Override
    protected void startup() {
        init();

        show(view);
    }

    @Override
    protected void configureWindow(Window root) {
    }

    protected int getAndIncSearchCount() {
        return searchCount++;
    }

    protected void clearDownloadTable() {
        clearTable(downloadTableModel);
    }

    protected void clearSharedTable() {
        clearTable(sharedTableModel);
    }

    protected TableModel getSharedModel() {
        return sharedTableModel;
    }

    protected TableModel getDownloadModel() {
        return downloadTableModel;
    }

    protected TableModel getActiveHostsTableModel() {
        return activeHostsTableModel;
    }

    protected TableModel getSearchModel(int id) {
        if (!searchTableModels.containsKey(new Integer(id))) {
            Object columns[] = new Object[SearchRecord.getColumnsCount()];
            columns[SearchRecord.getNamePos()] = "Name";
            //columns[SearchRecord.getHitratePos()] = "Hit rate";
            columns[SearchRecord.getSizePos()] = "Size";
            columns[SearchRecord.getHostPos()] = "Host";
            columns[SearchRecord.getHashPos()] = "Hash value";
            DefaultTableModel model = new DefaultTableModel(columns, 0) {

                @Override
                public boolean isCellEditable(int row, int column) {
                    return false;
                }
            };
            searchTableModels.put(new Integer(id), model);
            return model;
        }

        return searchTableModels.get(new Integer(id));
    }

    /*protected void disposeView() {

    }
     */
    protected void outQuit() {
        //showPleaseWaitWin();
        if (JOptionPane.showConfirmDialog(
                getMainFrame(),
                "Would you like to exit?",
                "Confirm exit",
                JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
            return;
        }
        this.shutdown();
        this.hide(view);
        this.guiCtrl.writeOutCommand(
                new ControllerCommand(ControllerCommand.ControllerCommandType.GUI_EXIT));
    }

    protected void outDelShared(String filename, String path) {
        guiCtrl.writeOutCommand(
                new CC_GUIDelShared(path, filename));
    }

    void outDelDownload(Integer id) {
        File file = downloadingFiles.get(id - 1);
        guiCtrl.writeOutCommand(new CC_GUIDelDownload(file));
    }

    protected void outStartDownload(int downloadId) {
        File file;
        try {
            file = getDownloadFile(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIStartDownloadFile(file));
    }

    protected void outStopDownload(int downloadId) {
        File file;
        try {
            file = getDownloadFile(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIStopDownloadFile(file));
    }

    protected void outMoveDown(int downloadId) {
        DownloadFile file;
        try {
            file = getDownloadFile(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        int position = downloadingFiles.indexOf(file);
        if (position == -1) {
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIMoveDownloadTask(file, position + 1));
    }

    protected void outMoveUp(int downloadId) {
        DownloadFile file;
        try {
            file = getDownloadFile(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        int position = downloadingFiles.indexOf(file);
        if (position == -1) {
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIMoveDownloadTask(file, position - 1));
    }

    private DownloadFile getDownloadFile(int id) throws IndexOutOfBoundsException {
        return downloadingFiles.get(id);
    }

    protected void outMoveTop(int downloadId) {
        DownloadFile file;
        try {
            file = downloadingFiles.get(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        int position = downloadingFiles.indexOf(file);
        if (position == -1) {
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIMoveDownloadTask(file, 0));
    }

    protected void outMoveBottom(int downloadId) {
        DownloadFile file;
        try {
            file = downloadingFiles.get(downloadId);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            return;
        }
        int position = downloadingFiles.indexOf(file);
        if (position == -1) {
            return;
        }
        guiCtrl.writeOutCommand(new CC_GUIMoveDownloadTask(file, downloadingFiles.size() - 1));
    }

    protected void outSearchFile(int idSearch, int minSize, int maxSize, String name) {
        searchResults.put(idSearch, new LinkedList<File>());
        guiCtrl.writeOutCommand(new CC_GUISearch(idSearch, name, minSize, maxSize));
    }

    protected void outSetProperties(int maxActiveDownVal, int connections) {
        guiCtrl.writeOutCommand(new CC_GUISetPreferences(maxActiveDownVal, connections));
    }

    protected void displayPropertiesWinUserReq() {
        getPropertiesRequested =
                true;
        outGetProperties();
        showPleaseWaitWin();
    }

    protected void addHostToActive(String host) {
        if (host == null) {
            System.err.println("GUI: P2pApp: addHostToActive(): host is null");
            return;
        }
        if (getActiveHostsRequested) {
            GregorianCalendar calendar = new GregorianCalendar() {

                @Override
                public String toString() {
                    return this.get(Calendar.HOUR_OF_DAY) +
                            ":" + this.get(Calendar.MINUTE) +
                            ":" + this.get(Calendar.SECOND) +
                            " " + this.get(Calendar.YEAR) +
                            "-" + this.get(Calendar.MONTH) +
                            "-" + this.get(Calendar.DAY_OF_MONTH);
                }
            };

            if (activeHosts.containsKey(host)) {
                //host already on list - change only date
                Integer id = activeHosts.get(host);
                if (id == null) {
                    System.err.println("GUI: P2pApp: addHostToActive(): " +
                            "cannot find host on list");
                    return;
                }
                activeHostsTableModel.setValueAt(calendar, id, 1);
                activeHostsTableModel.fireTableCellUpdated(id, 1);
            } else {
                Object rowData[] = new Object[2];
                rowData[0] = host;
                rowData[1] = calendar;

                activeHostsTableModel.addRow(rowData);
                Integer newId = new Integer(
                        activeHostsTableModel.getRowCount() - 1);
                activeHosts.put(host, newId);
                activeHostsTableModel.fireTableRowsInserted(
                        newId.intValue() - 1,
                        newId.intValue() - 1);
            }
        } else {
            System.err.println("GUI: P2pApp: addHostToActive(): user didn't request this!");
        }
    }

    protected void showActiveHostsReq() {
        if (!getActiveHostsRequested) {
            getActiveHostsRequested = true;
            outGetActiveHosts();

            view.showActiveHostsPanel();
        }

    }

    protected void closeActiveHostsPanelReq() {
        if (getActiveHostsRequested) {
            view.closeActiveHostsPanel();
            getActiveHostsRequested =
                    false;
            clearTable(activeHostsTableModel);
            activeHosts.clear();
        }

    }

    protected boolean isAllStoped() {
        boolean stop = true;
        ListIterator<DownloadFile> iter = downloadingFiles.listIterator();
        DownloadFile file;

        while (iter.hasNext()) {
            file = iter.next();
            if (!file.getDownloadState().equals(DownloadStateType.STOPPED)) {
                stop = false;
                break;

            }


        }
        return stop;
    }

    private void outGetProperties() {
        guiCtrl.writeOutCommand(new ControllerCommand(
                ControllerCommand.ControllerCommandType.GUI_GET_PREFERENCES));
    }

    private void outGetActiveHosts() {
        guiCtrl.writeOutCommand(new ControllerCommand(
                ControllerCommand.ControllerCommandType.GUI_GET_ACTIVE_HOSTS));
    }

    private void init() {
        Object[] columnNames = {"Filename", "Path"};
        sharedTableModel =
                new DefaultTableModel(columnNames, 0) {

                    @Override
                    public boolean isCellEditable(int row, int column) {
                        return false;
                    }
                };
        Object[] columnNames2 = {
            "Lp.",
            "Name",
            "Status",
            "Progress"};
        downloadTableModel =
                new DefaultTableModel(columnNames2, 0) {

                    @Override
                    public boolean isCellEditable(int row, int column) {
                        return false;
                    }
                };

        Object[] columnNames3 = {
            "Host",
            "Time"
        };

        activeHosts = new LinkedHashMap<String, Integer>();
        activeHostsTableModel = new DefaultTableModel(columnNames3, 0);
        manager = this;
        view = new P2pView(this);
        waitingWin = new PleaseWaitWindow(view.getFrame(), true);
        waitingWin.setVisible(false);
        searchTableModels = new HashMap<Integer, DefaultTableModel>();
        searchResults = new HashMap<Integer, List<File>>();
        super.addExitListener(new ExitListener() {

            public boolean canExit(EventObject arg0) {
                System.out.println("GUI: P2pApp: request to close app");
                P2pApp.getManager().outQuit();
                return false;
            }

            public void willExit(EventObject arg0) {
                System.err.println("GUI: P2pApp: closing");
                return;

            }
        });
    }

    private void clearTable(DefaultTableModel model) {
        for (int i = model.getRowCount() - 1; i >=
                0; i--) {
            model.removeRow(i);
        }

        model.fireTableDataChanged();
    }

    /**
     * It is blocking method because waitingWin is modal window
     */
    private void showPleaseWaitWin() {
        //can be executed after hideWitingWin so it shouldn't be displayed
        if (getActiveHostsRequested) {
            waitingWin.setVisible(true);
        }

    }

    private void hideWaitingWin() {
        waitingWin.setVisible(false);
    }
}
